#ifndef EVM_FILE_H
#define EVM_FILE_H

#include <fcntl.h>
#include <string>
#include <sys/uio.h>
#include <sys/stat.h>


namespace evm {

        /**
         * Convenience wrappers around some commonly used system calls.  The *NoInt
         * wrappers retry on EINTR.  The *Full wrappers retry on EINTR and also loop
         * until all data is written.  Note that *Full wrappers weaken the thread
         * semantics of underlying system calls.
         */
        int openNoInt(const char* name, int flags, mode_t mode = 0666);
        int closeNoInt(int fd);
        int dupNoInt(int fd);
        int dup2NoInt(int oldfd, int newfd);
        int fsyncNoInt(int fd);
        int fdatasyncNoInt(int fd);
        int ftruncateNoInt(int fd, off_t len);
        int truncateNoInt(const char* path, off_t len);
        int flockNoInt(int fd, int operation);
        int shutdownNoInt(int fd, int how);

        ssize_t readNoInt(int fd, void* buf, size_t n);
        ssize_t preadNoInt(int fd, void* buf, size_t n, off_t offset);
        ssize_t readvNoInt(int fd, const iovec* iov, int count);

        ssize_t writeNoInt(int fd, const void* buf, size_t n);
        ssize_t pwriteNoInt(int fd, const void* buf, size_t n, off_t offset);
        ssize_t writevNoInt(int fd, const iovec* iov, int count);

        /**
         * Wrapper around read() (and pread()) that, in addition to retrying on
         * EINTR, will loop until all data is read.
         *
         * This wrapper is only useful for blocking file descriptors (for non-blocking
         * file descriptors, you have to be prepared to deal with incomplete reads
         * anyway), and only exists because POSIX allows read() to return an incomplete
         * read if interrupted by a signal (instead of returning -1 and setting errno
         * to EINTR).
         *
         * Note that this wrapper weakens the thread safety of read(): the file pointer
         * is shared between threads, but the system call is atomic.  If multiple
         * threads are reading from a file at the same time, you don't know where your
         * data came from in the file, but you do know that the returned bytes were
         * contiguous.  You can no longer make this assumption if using readFull().
         * You should probably use pread() when reading from the same file descriptor
         * from multiple threads simultaneously, anyway.
         *
         * Note that readvFull and preadvFull require iov to be non-const, unlike
         * readv and preadv.  The contents of iov after these functions return
         * is unspecified.
         */
        ssize_t readFull(int fd, void* buf, size_t n);
        ssize_t preadFull(int fd, void* buf, size_t n, off_t offset);
        ssize_t readvFull(int fd, iovec* iov, int count);

        /**
         * Similar to readFull and preadFull above, wrappers around write() and
         * pwrite() that loop until all data is written.
         *
         * Generally, the write() / pwrite() system call may always write fewer bytes
         * than requested, just like read().  In certain cases (such as when writing to
         * a pipe), POSIX provides stronger guarantees, but not in the general case.
         * For example, Linux (even on a 64-bit platform) won't write more than 2GB in
         * one write() system call.
         *
         * Note that writevFull and pwritevFull require iov to be non-const, unlike
         * writev and pwritev.  The contents of iov after these functions return
         * is unspecified.
         */
        ssize_t writeFull(int fd, const void* buf, size_t n);
        ssize_t pwriteFull(int fd, const void* buf, size_t n, off_t offset);
        ssize_t writevFull(int fd, iovec* iov, int count);

        class File {
        public:
            File() = default;

            /* Create a File object from an existing file descriptor.
             * Takes ownership of the file descriptor if ownsFd is true. */
            explicit File(int fd, bool owns_fd = false);

            File(const char* name, int flags = O_RDONLY, mode_t mode = 0666);
            File(const std::string& name, int flags = O_RDONLY, mode_t mode = 0666);

            ~File();

            // movable
            File(File&&) noexcept;
            File& operator=(File&&);

            /* If we own the file descriptor, close the file and throw on error.
             * Otherwise, do nothing. */
            void close();

            /* Closes the file (if owned).  Returns true on success, false (and sets
             * errno) on error. */
            bool closeNoThrow();

            /* Returns and releases the file descriptor; no longer owned by this File.
             * Returns -1 if the File object didn't wrap a file. */
            int release() noexcept;

            /* Swap this File with another. */
            void swap(File& other);

            int fd() const { return fd_; }
            std::string name() const { return name_; }

            explicit operator bool() const {
                return fd_ != -1;
            }

            bool read(void* buf, size_t& count);
            bool read(void* buf, const size_t& count);
            bool write(const void* buf, size_t& count);
            bool write(const void* buf, const size_t& count);
            size_t size();

            /* Read all bytes from file, maybe throw runtime_error. */
            static std::string read_all(const char* name);
            static std::string read_all(const std::string& name);
            /* Write all bytes to file. */
            static void write_all(const char* name, const std::string& contents);
            static void write_all(const std::string& name, const std::string& contents);

        private:
            File(const File&) = delete;
            File& operator=(const File&) = delete;

            std::string name_;
            int fd_{-1};
            bool owns_fd_{false};
        };
}

#endif //EVM_FILE_H
